all repos — caroster @ fe359e3d0bd28293a74106f4ce391f5127bce55f

[Octree] Group carpool to your event https://caroster.io

frontend/pages/e/[uuid]/details.tsx (view raw)

  1import moment from 'moment';
  2import Tooltip from '@mui/material/Tooltip';
  3import IconButton from '@mui/material/IconButton';
  4import Box from '@mui/material/Box';
  5import Link from '@mui/material/Link';
  6import Card from '@mui/material/Card';
  7import Container from '@mui/material/Container';
  8import TextField from '@mui/material/TextField';
  9import Typography from '@mui/material/Typography';
 10import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
 11import EventIcon from '@mui/icons-material/Event';
 12import TuneIcon from '@mui/icons-material/Tune';
 13import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
 14import {useTheme} from '@mui/material/styles';
 15import {DatePicker} from '@mui/x-date-pickers/DatePicker';
 16import {PropsWithChildren, useState} from 'react';
 17import {useTranslation} from 'react-i18next';
 18import pageUtils from '../../../lib/pageUtils';
 19import ShareEvent from '../../../containers/ShareEvent';
 20import useEventStore from '../../../stores/useEventStore';
 21import useToastStore from '../../../stores/useToastStore';
 22import EventLayout, {TabComponent} from '../../../layouts/Event';
 23import {
 24  EventByUuidDocument,
 25  useUpdateEventMutation,
 26} from '../../../generated/graphql';
 27import PlaceInput from '../../../containers/PlaceInput';
 28
 29interface Props {
 30  eventUUID: string;
 31  announcement?: string;
 32}
 33
 34const Page = (props: PropsWithChildren<Props>) => {
 35  return <EventLayout {...props} Tab={DetailsTab} />;
 36};
 37
 38const DetailsTab: TabComponent = ({}) => {
 39  const {t} = useTranslation();
 40  const theme = useTheme();
 41  const [updateEvent] = useUpdateEventMutation();
 42  const addToast = useToastStore(s => s.addToast);
 43  const setEventUpdate = useEventStore(s => s.setEventUpdate);
 44  const event = useEventStore(s => s.event);
 45  const [isEditing, setIsEditing] = useState(false);
 46
 47  const onSave = async e => {
 48    try {
 49      const {uuid, ...data} = event;
 50      const {id, travels, waitingPassengers, __typename, ...input} = data;
 51      await updateEvent({
 52        variables: {
 53          uuid,
 54          eventUpdate: {
 55            ...input,
 56          },
 57        },
 58        refetchQueries: ['eventByUUID'],
 59      });
 60      setIsEditing(false);
 61    } catch (error) {
 62      console.error(error);
 63      addToast(t('event.errors.cant_update'));
 64    }
 65  };
 66
 67  const modifyButton = isEditing ? (
 68    <Tooltip
 69      title={t('event.details.save')}
 70      sx={{
 71        position: 'absolute',
 72        top: theme.spacing(2),
 73        right: theme.spacing(2),
 74      }}
 75    >
 76      <IconButton color="primary" onClick={onSave}>
 77        <CheckCircleOutlineIcon />
 78      </IconButton>
 79    </Tooltip>
 80  ) : (
 81    <Tooltip
 82      title={t('event.details.modify')}
 83      sx={{
 84        position: 'absolute',
 85        top: theme.spacing(2),
 86        right: theme.spacing(2),
 87      }}
 88    >
 89      <IconButton color="primary" onClick={() => setIsEditing(true)}>
 90        <TuneIcon />
 91      </IconButton>
 92    </Tooltip>
 93  );
 94
 95  if (!event) return null;
 96
 97  return (
 98    <Box
 99      sx={{
100        position: 'relative',
101      }}
102    >
103      <Container
104        sx={{
105          p: 4,
106          mt: 6,
107          mb: 11,
108          mx: 0,
109          [theme.breakpoints.down('md')]: {
110            p: 2,
111          },
112        }}
113      >
114        <Card
115          sx={{
116            position: 'relative',
117            maxWidth: '100%',
118            width: '350px',
119            p: 2,
120          }}
121        >
122          <Typography variant="h4" pb={2}>
123            {t('event.details')}
124          </Typography>
125          {modifyButton}
126          <Box pt={2} pr={1.5}>
127            <Typography variant="overline">{t('event.fields.name')}</Typography>
128            <Typography variant="body1">
129              {isEditing ? (
130                <TextField
131                  size="small"
132                  fullWidth
133                  value={event.name}
134                  onChange={e => setEventUpdate({name: e.target.value})}
135                  name="name"
136                  id="EditEventName"
137                />
138              ) : (
139                <Typography variant="body1" id="EventName">
140                  {event.name ?? t('event.fields.empty')}
141                </Typography>
142              )}
143            </Typography>
144          </Box>
145          <Box pt={2} pr={1.5}>
146            <Typography variant="overline">{t('event.fields.date')}</Typography>
147            {isEditing ? (
148              <Typography variant="body1">
149                <DatePicker
150                  slotProps={{
151                    textField: {
152                      size: 'small',
153                      id: `EditEventDate`,
154                      fullWidth: true,
155                      placeholder: t('event.fields.date_placeholder'),
156                    },
157                  }}
158                  format="DD/MM/YYYY"
159                  value={moment(event.date)}
160                  onChange={date =>
161                    setEventUpdate({
162                      date: !date ? null : moment(date).format('YYYY-MM-DD'),
163                    })
164                  }
165                />
166              </Typography>
167            ) : (
168              <Box position="relative">
169                <Typography variant="body1" id="EventDate">
170                  {event.date
171                    ? moment(event.date).format('DD/MM/YYYY')
172                    : t('event.fields.empty')}
173                </Typography>
174                <EventIcon
175                  color="action"
176                  sx={{
177                    position: 'absolute',
178                    right: theme.spacing(-0.5),
179                    top: 0,
180                  }}
181                />
182              </Box>
183            )}
184          </Box>
185          <Box pt={2} pr={1.5}>
186            <Typography variant="overline">
187              {t('event.fields.address')}
188            </Typography>
189            {isEditing ? (
190              <PlaceInput
191                place={event.address}
192                onSelect={({location, place}) => {
193                  setEventUpdate({
194                    address: place,
195                    latitude: location[1],
196                    longitude: location[0],
197                  });
198                }}
199              />
200            ) : (
201              <Box position="relative">
202                <Typography variant="body1" id="EventAddress" sx={{pr: 3}}>
203                  {event.address ? (
204                    <Link
205                      target="_blank"
206                      rel="noreferrer"
207                      href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
208                        event.address
209                      )}`}
210                      onClick={e => e.preventDefault}
211                    >
212                      {event.address}
213                    </Link>
214                  ) : (
215                    t('event.fields.empty')
216                  )}
217                </Typography>
218                <PlaceOutlinedIcon
219                  color="action"
220                  sx={{
221                    position: 'absolute',
222                    right: theme.spacing(-0.5),
223                    top: 0,
224                  }}
225                />
226              </Box>
227            )}
228          </Box>
229          <Box pt={2} pr={1.5}>
230            <Typography variant="overline">
231              {t('event.fields.description')}
232            </Typography>
233            {isEditing ? (
234              <Typography variant="body1">
235                <TextField
236                  fullWidth
237                  multiline
238                  maxRows={4}
239                  inputProps={{maxLength: 250}}
240                  value={event.description || ''}
241                  onChange={e => setEventUpdate({description: e.target.value})}
242                  id={`EditEventDescription`}
243                  name="description"
244                />
245              </Typography>
246            ) : (
247              <Typography variant="body1" id="EventDescription" sx={{pr: 3}}>
248                {event.description ?? t('event.fields.empty')}
249              </Typography>
250            )}
251          </Box>
252          {!isEditing && (
253            <ShareEvent
254              title={`Caroster ${event.name}`}
255              sx={{width: '100%', mt: 2}}
256            />
257          )}
258        </Card>
259      </Container>
260    </Box>
261  );
262};
263
264export const getServerSideProps = pageUtils.getServerSideProps(
265  async (context, apolloClient) => {
266    const {uuid} = context.query;
267    const {host = ''} = context.req.headers;
268    let event = null;
269
270    // Fetch event
271    try {
272      const {data} = await apolloClient.query({
273        query: EventByUuidDocument,
274        variables: {uuid},
275      });
276      event = data?.eventByUUID?.data;
277    } catch (error) {
278      return {
279        notFound: true,
280      };
281    }
282
283    return {
284      props: {
285        eventUUID: uuid,
286        metas: {
287          title: event?.attributes?.name || '',
288          url: `https://${host}${context.resolvedUrl}`,
289        },
290      },
291    };
292  }
293);
294export default Page;